Micron Document
πŸŽ–οΈGitΠ―Ρ€Π°πŸŽ–οΈ


Displaying Raw β€’ View rendered β€’ Download

specs/006-kmp-project-structure/spec.md bd2863243bab6eb213401d949839a2bc74dde7e2 (bd286324) Text, 22.88 KB

Tc9d1d9# Feature Specification: KMP Recommended Project Structure Alignment

**Feature Branch**: Ta5d6ff`020-kmp-project-structure`
**Created**: 2025-07-15
**Status**: Draft
**Input**: User description: "Restructure Meshtastic Android project to align with the updated Kotlin Multiplatform recommended project structure"
**Cross-Platform Spec**: N/A β€” this is a build/infrastructure change with no user-facing behavior changes across platforms

Tc9d1d9## Summary

The Meshtastic Android project should be aligned with the official Kotlin Multiplatform recommended project structure as documented in the [Tff7b72KMP project structure guide](Te6edf3https://kotlinlang.org/docs/multiplatform/multiplatform-project-recommended-structure.html) and the [Tff7b72JetBrains blog post on the new KMP default structure](Te6edf3https://blog.jetbrains.com/kotlin/2026/05/new-kmp-default-structure/). The project already runs on AGP 9.2.1 and already applies the Ta5d6ff`com.android.kotlin.multiplatform.library` plugin via convention plugins, but an audit reveals that **all 27 KMP modules still contain legacy `android {}` blocks inside `kotlin {}`** that should be migrated to the recommended Ta5d6ff`kotlin.androidLibrary {}` top-level DSL. This effort focuses on completing that migration, validating module boundaries, and ensuring convention plugins enforce the canonical patterns β€” so the project fully conforms to the new structure with clear module responsibilities.

Tc9d1d9## Goals

Tff7b721. **Migrate all legacy `android {}` blocks** inside Ta5d6ff`kotlin {}` to the recommended Ta5d6ff`kotlin.androidLibrary {}` top-level DSL across all 27 KMP modules in Ta5d6ff`core/` and Ta5d6ff`feature/`.
Tff7b722. **Validate and harden convention plugins** so that Ta5d6ff`KmpLibraryConventionPlugin` and Ta5d6ff`KmpFeatureConventionPlugin` configure Android targets exclusively through the new plugin's DSL, preventing legacy patterns from being reintroduced.
Tff7b723. **Confirm entry-point module separation** β€” Ta5d6ff`app/` (Android) and Ta5d6ff`desktop/` (JVM) are already separate entry-point modules with no shared business logic, satisfying the AGP 9 mandatory requirement.
Tff7b724. **Document the module boundary model** β€” map the current Ta5d6ff`core/` (shared logic) and Ta5d6ff`feature/` (shared UI + logic) organization to the recommended Ta5d6ff`sharedLogic` / Ta5d6ff`sharedUI` categories, confirming they already satisfy the recommended split.
Tff7b725. **Ensure forward compatibility** β€” verify the project structure is compatible with future KMP toolchain updates and potential new targets (iOS, web) without requiring another structural overhaul.

Tc9d1d9## Non-Goals

Tff7b72- **Introducing iOS, web, or server modules** β€” while the recommended structure supports these, this spec scopes only the existing Android + Desktop targets.
Tff7b72- **Renaming modules to match the default template names** β€” the recommended structure uses Ta5d6ff`shared`, Ta5d6ff`androidApp`, Ta5d6ff`desktopApp` as defaults, but the project's existing Ta5d6ff`core/`, Ta5d6ff`feature/`, Ta5d6ff`app/`, Ta5d6ff`desktop/` naming is equally valid and will not be renamed for cosmetic alignment.
Tff7b72- **Changing application behavior or UI** β€” this is purely a build infrastructure and module organization change. No user-facing functionality changes.
Tff7b72- **Migrating away from existing technology choices** β€” Koin, Ktor, Room KMP, Compose Multiplatform, and Navigation 3 remain as-is per the constitution.
Tff7b72- **Migrating `feature:widget` away from `com.android.library`** β€” this module is genuinely Android-only (Glance app widgets) and correctly uses the Android library plugin, not the KMP library plugin.

Tc9d1d9## User Scenarios & Testing *(mandatory)*

Tc9d1d9### User Story 1 - Developer Builds Successfully After Restructuring (Priority: P1)

As a contributor, I want the project to build successfully on both Android and Desktop targets after any structural changes, so that my development workflow is uninterrupted.

**Why this priority**: A broken build blocks all development. This is the absolute minimum bar for any structural change.

**Independent Test**: Can be fully tested by running Ta5d6ff`./gradlew assembleDebug`, Ta5d6ff`./gradlew :desktop:packageUberJarForCurrentOS`, and Ta5d6ff`./gradlew allTests` and verifying all pass.

**Acceptance Scenarios**:

Tff7b721. **Given** the restructured project, **When** a developer runs the full Android build (Ta5d6ff`assembleDebug`), **Then** the build completes with zero errors.
Tff7b722. **Given** the restructured project, **When** a developer runs the Desktop build (Ta5d6ff`:desktop:packageUberJarForCurrentOS`), **Then** the build completes with zero errors.
Tff7b723. **Given** the restructured project, **When** a developer runs all tests (Ta5d6ff`allTests`), **Then** all existing tests pass with no regressions.
Tff7b724. **Given** the restructured project with Ta5d6ff`DESKTOP_ONLY=true`, **When** a developer builds without Android SDK, **Then** the desktop-only build succeeds as before.

---

Tc9d1d9### User Story 2 - Convention Plugins Reflect Recommended Patterns (Priority: P2)

As a build maintainer, I want the convention plugins in Ta5d6ff`build-logic/` to apply the correct KMP plugin configuration patterns recommended by JetBrains, so that adding new modules follows a clear, documented convention.

**Why this priority**: Convention plugins are the enforcement layer for project structure. If they are correct, individual module configurations stay consistent.

**Independent Test**: Can be tested by verifying that Ta5d6ff`KmpLibraryConventionPlugin` applies the Ta5d6ff`com.android.kotlin.multiplatform.library` plugin and that no KMP library module uses the legacy Ta5d6ff`com.android.library` plugin.

**Acceptance Scenarios**:

Tff7b721. **Given** the Ta5d6ff`meshtastic.kmp.library` convention plugin, **When** applied to a KMP module, **Then** it configures the module using Ta5d6ff`com.android.kotlin.multiplatform.library` (not Ta5d6ff`com.android.library`).
Tff7b722. **Given** the Ta5d6ff`meshtastic.kmp.feature` convention plugin, **When** applied to a feature module, **Then** it inherits the correct KMP library plugin chain without legacy Android library configuration.
Tff7b723. **Given** a new core module is created, **When** a developer applies Ta5d6ff`meshtastic.kmp.library`, **Then** the module follows the recommended structure with Ta5d6ff`kotlin.androidLibrary {}` configuration.

---

Tc9d1d9### User Story 3 - Module Boundary Clarity for New Contributors (Priority: P3)

As a new contributor, I want a clear separation between entry-point modules, shared business logic modules, and shared UI modules, so that I know where to add new code based on its purpose.

**Why this priority**: Clear boundaries reduce onboarding friction and prevent architectural drift over time.

**Independent Test**: Can be tested by reviewing the module dependency graph and verifying that entry-point modules (Ta5d6ff`app/`, Ta5d6ff`desktop/`) do not contain shared logic, and that Ta5d6ff`core/` modules do not depend on Ta5d6ff`feature/` modules.

**Acceptance Scenarios**:

Tff7b721. **Given** the module dependency graph, **When** analyzing Ta5d6ff`app/` dependencies, **Then** Ta5d6ff`app/` depends on Ta5d6ff`core/` and Ta5d6ff`feature/` modules but contains no shared business logic.
Tff7b722. **Given** the module dependency graph, **When** analyzing Ta5d6ff`desktop/` dependencies, **Then** Ta5d6ff`desktop/` depends on Ta5d6ff`core/` and Ta5d6ff`feature/` modules but contains no shared business logic.
Tff7b723. **Given** any Ta5d6ff`core/` module, **When** examining its dependencies, **Then** it does not depend on any Ta5d6ff`feature/` module (unidirectional flow preserved).

---

Tc9d1d9### User Story 4 - Legacy DSL Block Migration (Priority: P1)

As a build maintainer, I want all 27 KMP modules' legacy Ta5d6ff`android {}` blocks inside Ta5d6ff`kotlin {}` migrated to the recommended Ta5d6ff`kotlin.androidLibrary {}` top-level DSL, so that the build configuration fully uses the new plugin's canonical API.

**Why this priority**: The project already runs AGP 9.2.1 and applies the Ta5d6ff`com.android.kotlin.multiplatform.library` plugin, but every KMP module still configures its Android target using the legacy Ta5d6ff`android {}` block inside Ta5d6ff`kotlin {}` (for namespace, resource settings, etc.). This is the primary gap between the current state and full alignment with the recommended structure. It ties with P1 because the build already works β€” this is about eliminating technical debt before it becomes a blocker.

**Independent Test**: Can be tested by searching all Ta5d6ff`core/` and Ta5d6ff`feature/` KMP module Ta5d6ff`build.gradle.kts` files for Ta5d6ff`android {` blocks inside Ta5d6ff`kotlin {}` β€” zero should remain after migration. All configuration should appear in Ta5d6ff`kotlin.androidLibrary {}` top-level blocks or be handled by convention plugins.

**Acceptance Scenarios**:

Tff7b721. **Given** any KMP library module in Ta5d6ff`core/` or Ta5d6ff`feature/`, **When** examining its Ta5d6ff`build.gradle.kts`, **Then** it contains no Ta5d6ff`android {}` block inside Ta5d6ff`kotlin {}` β€” Android configuration uses the Ta5d6ff`kotlin.androidLibrary {}` top-level DSL or is delegated to convention plugins.
Tff7b722. **Given** the Ta5d6ff`configureKotlinMultiplatform()` helper in build-logic, **When** it configures Android targets, **Then** it uses Ta5d6ff`KotlinMultiplatformAndroidLibraryTarget` APIs from the new plugin, not legacy Ta5d6ff`android {}` extension configuration.
Tff7b723. **Given** the full project with all 27 modules migrated, **When** building Android and Desktop targets, **Then** all builds succeed with zero errors.

---

Tc9d1d9### Edge Cases

Tff7b72- What happens when the Ta5d6ff`DESKTOP_ONLY` mode is active and no Android SDK is available? The restructured plugins must continue to conditionally skip Android plugin application (as the current Ta5d6ff`isDesktopOnly` guard already does).
Tff7b72- How does the system handle modules that are genuinely Android-only (e.g., Ta5d6ff`core:api`, Ta5d6ff`core:barcode`, Ta5d6ff`screenshot-tests`)? These modules should continue using Ta5d6ff`com.android.library` or Ta5d6ff`com.android.application` as appropriate β€” the KMP library plugin applies only to multiplatform modules.
Tff7b72- What happens if a module currently uses Ta5d6ff`com.android.library` but should be KMP? The migration path must be documented and executed per-module, with build verification at each step.

Tc9d1d9## Architecture

Tc9d1d9### Gap Analysis: Current State vs Recommended Structure

The audit reveals the project is **substantially aligned** with the recommended KMP structure, with one significant gap remaining:

Ta5d6ff```
AREA STATUS FINDING
==== ====== =======

Entry-point separation βœ… DONE app/ (Android) and desktop/ (JVM) are
already separate entry-point modules.
AGP 9 mandatory requirement satisfied.

KMP library plugin adoption βœ… DONE KmpLibraryConventionPlugin already applies
com.android.kotlin.multiplatform.library
via the android-kotlin-multiplatform-library
catalog alias. No androidTarget {} calls.

AGP version βœ… DONE Project already runs AGP 9.2.1.

Legacy android {} blocks in kotlin {} ⚠️ GAP All 27 KMP modules still configure Android
via android {} blocks INSIDE kotlin {} for
namespace, androidResources, withHostTest.
These should migrate to kotlin.androidLibrary {}
top-level DSL blocks.

Module boundary model βœ… DONE core/ = shared business logic (β‰ˆ sharedLogic)
feature/ = shared UI + logic (β‰ˆ sharedUI)
Unidirectional: app/desktop β†’ feature β†’ core

Dependency direction βœ… DONE No reverse dependencies. core/ does not
depend on feature/. Unidirectional flow.

DESKTOP_ONLY mode βœ… DONE Conditional Android plugin skipping via
isDesktopOnly guard in convention plugins.

Android-only modules βœ… DONE feature:widget uses com.android.library
correctly. core:api, core:barcode also
Android-only with appropriate plugins.

Convention plugin architecture ⚠️ GAP configureKotlinMultiplatform() helper
may contain legacy android {} configuration
patterns alongside the new plugin's API.
Needs audit and cleanup.
```

Tc9d1d9### Affected Modules (Legacy `android {}` blocks to migrate)

**Core modules (19)**:
Ta5d6ff`core:ble`, Ta5d6ff`core:common`, Ta5d6ff`core:data`, Ta5d6ff`core:database`, Ta5d6ff`core:datastore`, Ta5d6ff`core:di`, Ta5d6ff`core:domain`, Ta5d6ff`core:model`, Ta5d6ff`core:navigation`, Ta5d6ff`core:network`, Ta5d6ff`core:nfc`, Ta5d6ff`core:prefs`, Ta5d6ff`core:proto`, Ta5d6ff`core:repository`, Ta5d6ff`core:resources`, Ta5d6ff`core:service`, Ta5d6ff`core:takserver`, Ta5d6ff`core:testing`, Ta5d6ff`core:ui`

**Feature modules (8 β€” excluding widget)**:
Ta5d6ff`feature:connections`, Ta5d6ff`feature:firmware`, Ta5d6ff`feature:intro`, Ta5d6ff`feature:map`, Ta5d6ff`feature:messaging`, Ta5d6ff`feature:node`, Ta5d6ff`feature:settings`, Ta5d6ff`feature:wifi-provision`

**Not affected** (correctly using Ta5d6ff`com.android.library` or Ta5d6ff`com.android.application`):
Ta5d6ff`app/`, Ta5d6ff`desktop/`, Ta5d6ff`feature:widget`, Ta5d6ff`core:api`, Ta5d6ff`core:barcode`, Ta5d6ff`screenshot-tests`

Tc9d1d9### Mapping to Recommended Structure

Ta5d6ff```
RECOMMENDED STRUCTURE MESHTASTIC EQUIVALENT NOTES
===================== ======================= =====

androidApp/ app/ βœ… Same role, different name
- kotlin.android (naming is cosmetic)
+ com.android.application

desktopApp/ desktop/ βœ… Same role, different name
- kotlin.jvm + compose

shared/ (single shared mod) core/ + feature/ βœ… Already modularized further
OR (27+ KMP modules) than the default; this is the
sharedLogic/ + sharedUI/ core/ β‰ˆ sharedLogic recommended "advanced" pattern
feature/ β‰ˆ sharedUI

build-logic/ build-logic/ βœ… Project-specific convention
plugins (not prescribed by
JetBrains but aligned)
```

Tc9d1d9### Reference Projects Comparison

The JetBrains blog cites Ta5d6ff`kotlinconf-app`, Ta5d6ff`KMP-App-Template`, and Ta5d6ff`RSS Reader` as reference implementations. The Meshtastic project is more mature and more modularized than any of these, having already decomposed the monolithic Ta5d6ff`shared` module into granular Ta5d6ff`core/` and Ta5d6ff`feature/` modules. This is the recommended evolution path for larger projects.

Tc9d1d9### Key Components

| Component | Module / File | Purpose |
| -------------------------------- | ---------------------------------------------------------- | ------------------------------------------------------------- |
| KmpLibraryConventionPlugin | Ta5d6ff`build-logic/convention/.../KmpLibraryConventionPlugin.kt` | Applies KMP + Android library plugins to shared modules |
| KmpFeatureConventionPlugin | Ta5d6ff`build-logic/convention/.../KmpFeatureConventionPlugin.kt` | Composite plugin for feature modules (KMP + Compose + Koin) |
| KmpJvmAndroidConventionPlugin | Ta5d6ff`build-logic/convention/.../KmpJvmAndroidConventionPlugin.kt` | Configures jvmAndroidMain shared source set |
| AndroidLibraryConventionPlugin | Ta5d6ff`build-logic/convention/.../AndroidLibraryConventionPlugin.kt` | Legacy Android library plugin (for Android-only modules) |
| configureKotlinMultiplatform() | Ta5d6ff`build-logic/convention/.../KotlinAndroid.kt` | Shared Kotlin/Android configuration helper |
| settings.gradle.kts | Root Ta5d6ff`settings.gradle.kts` | Module registration and plugin management |

Tc9d1d9## Requirements *(mandatory)*

Tc9d1d9### Functional Requirements

Tff7b72- **FR-001**: All 27 KMP library modules (Ta5d6ff`core/*` except Ta5d6ff`core:api` and Ta5d6ff`core:barcode`, plus Ta5d6ff`feature/*` except Ta5d6ff`feature:widget`) MUST have their Ta5d6ff`android {}` blocks inside Ta5d6ff`kotlin {}` migrated to Ta5d6ff`kotlin.androidLibrary {}` top-level DSL blocks.
Tff7b72- **FR-002**: Entry-point modules (Ta5d6ff`app/`, Ta5d6ff`desktop/`) MUST remain as standalone application modules that depend on shared modules but contain no reusable business logic. This is already satisfied and MUST NOT regress.
Tff7b72- **FR-003**: The Ta5d6ff`KmpLibraryConventionPlugin` MUST configure Android target properties (namespace, compileSdk, minSdk, resource settings) exclusively through the Ta5d6ff`com.android.kotlin.multiplatform.library` plugin's API (Ta5d6ff`KotlinMultiplatformAndroidLibraryTarget`), not through legacy Ta5d6ff`android {}` extension blocks.
Tff7b72- **FR-004**: Android-only modules (Ta5d6ff`core:api`, Ta5d6ff`core:barcode`, Ta5d6ff`feature:widget`, Ta5d6ff`screenshot-tests`) MUST continue using Ta5d6ff`com.android.library` or Ta5d6ff`com.android.application` as they are not multiplatform modules.
Tff7b72- **FR-005**: The Ta5d6ff`DESKTOP_ONLY` build mode MUST continue functioning β€” when active, Android plugin application MUST be skipped for KMP modules (existing Ta5d6ff`isDesktopOnly` guard preserved).
Tff7b72- **FR-006**: Module dependency direction MUST remain unidirectional: Ta5d6ff`app/desktop β†’ feature β†’ core β†’ build-logic`. No reverse dependencies.
Tff7b72- **FR-007**: The Ta5d6ff`configureKotlinMultiplatform()` helper function MUST be audited and updated to remove any residual legacy configuration that duplicates or conflicts with the new plugin's target configuration.
Tff7b72- **FR-008**: Convention plugins MUST prevent future modules from using the legacy Ta5d6ff`android {}` pattern β€” applying Ta5d6ff`meshtastic.kmp.library` MUST automatically configure the Android target through the new plugin's DSL with no manual Ta5d6ff`android {}` block needed in the module's Ta5d6ff`build.gradle.kts`.

Tc9d1d9### Non-Functional Requirements

Tff7b72- **NFR-001**: The restructuring MUST NOT increase full-project build time by more than 5%.
Tff7b72- **NFR-002**: Gradle configuration cache, isolated projects, and parallel execution (Ta5d6ff`gradle.properties` settings) MUST remain functional after changes.
Tff7b72- **NFR-003**: All changes MUST be backward-compatible within a single migration PR β€” no intermediate broken states on the main branch.
Tff7b72- **NFR-004**: The migration MUST be documentable as a step-by-step checklist that other Meshtastic platform repositories can reference.

Tc9d1d9## Source-Set Impact

| Source Set | Impact | Justification |
| -------------- | --------------------------------------------------- | ---------------------------------------------------------------------------------------- |
| Ta5d6ff`commonMain` | No changes to source files | Business logic and UI remain in commonMain; only build configuration changes |
| Ta5d6ff`androidMain` | No changes to source files | Platform-specific code unchanged; plugin configuration changes only |
| Ta5d6ff`jvmMain` | No changes to source files | Desktop-specific code unchanged; plugin configuration changes only |
| Build files | Modified Ta5d6ff`build.gradle.kts` across 28 files (27 modules + 1 convention plugin) | Plugin IDs and configuration blocks updated to match recommended patterns |
| Convention plugins | Modified convention plugin Kotlin files | Updated to enforce recommended plugin application and configuration patterns |

Tc9d1d9## Privacy Assessment

Tff7b72- Tff7b72[x] No PII, location data, or cryptographic keys logged or exposed
Tff7b72- Tff7b72[x] No new network calls that transmit user data
Tff7b72- Tff7b72[x] Proto submodule (Ta5d6ff`core/proto`) not modified (read-only upstream)

This feature is purely a build infrastructure change with no runtime behavior changes.

Tc9d1d9## Success Criteria *(mandatory)*

Tc9d1d9### Measurable Outcomes

Tff7b72- **SC-001**: All 27 KMP library modules have zero Ta5d6ff`android {}` blocks inside Ta5d6ff`kotlin {}` β€” all Android configuration uses Ta5d6ff`kotlin.androidLibrary {}` top-level DSL or is handled by convention plugins.
Tff7b72- **SC-002**: Full project builds succeed on both Android (Ta5d6ff`assembleDebug`) and Desktop (Ta5d6ff`:desktop:packageUberJarForCurrentOS`) with zero new errors after migration.
Tff7b72- **SC-003**: All existing tests pass (Ta5d6ff`allTests`) with zero regressions after migration.
Tff7b72- **SC-004**: The Ta5d6ff`DESKTOP_ONLY` build mode continues to function correctly, building without Android SDK.
Tff7b72- **SC-005**: Build time for a clean Ta5d6ff`assembleDebug` does not increase by more than 5% compared to the pre-migration baseline.
Tff7b72- **SC-006**: A new contributor can add a new KMP module by applying one convention plugin (Ta5d6ff`meshtastic.kmp.library`) and the module is correctly configured for both Android and Desktop targets with no manual Ta5d6ff`android {}` block needed.
Tff7b72- **SC-007**: The Ta5d6ff`configureKotlinMultiplatform()` helper contains zero references to legacy Ta5d6ff`android {}` extension configuration β€” all Android target configuration goes through the new plugin's API.

Tc9d1d9## Assumptions

Tff7b72- All business logic and UI composables reside in Ta5d6ff`commonMain` source set (per Constitution Β§I, Β§III).
Tff7b72- The project already runs AGP 9.2.1 and applies Ta5d6ff`com.android.kotlin.multiplatform.library` in Ta5d6ff`KmpLibraryConventionPlugin` β€” the plugin adoption is complete; only the DSL migration from Ta5d6ff`android {}` to Ta5d6ff`kotlin.androidLibrary {}` remains.
Tff7b72- The Ta5d6ff`configureKotlinMultiplatform()` helper already uses Ta5d6ff`KotlinMultiplatformAndroidLibraryTarget` for compileSdk/minSdk configuration β€” any remaining legacy Ta5d6ff`android {}` blocks are in individual module Ta5d6ff`build.gradle.kts` files, not solely in convention plugins.
Tff7b72- No module uses the deprecated Ta5d6ff`androidTarget {}` call β€” this was verified by audit (zero matches found).
Tff7b72- The typical Ta5d6ff`android {}` block content in KMP modules is limited to Ta5d6ff`namespace` and Ta5d6ff`androidResources.enable = false` and occasionally Ta5d6ff`withHostTest {}` β€” migration should be mechanical.
Tff7b72- Android-only modules (Ta5d6ff`core:api`, Ta5d6ff`core:barcode`, Ta5d6ff`feature:widget`) are not candidates for the KMP library plugin migration β€” they correctly remain on Ta5d6ff`com.android.library`.
Tff7b72- The Gradle version catalog (Ta5d6ff`libs.versions.toml`) already declares the Ta5d6ff`android-kotlin-multiplatform-library` plugin alias pointing to AGP 9.2.1.
Tff7b72- The Ta5d6ff`jvmAndroidMain` shared source set pattern (used by some modules via Ta5d6ff`meshtastic.kmp.jvm.android`) is compatible with the new plugin and does not conflict with the Ta5d6ff`kotlin.androidLibrary {}` DSL.
Tff7b72- The recommended structure's module naming (Ta5d6ff`shared`, Ta5d6ff`androidApp`, Ta5d6ff`desktopApp`) is a default convention, not a requirement β€” the project's existing Ta5d6ff`core/`, Ta5d6ff`feature/`, Ta5d6ff`app/`, Ta5d6ff`desktop/` naming is equally valid per the JetBrains documentation.

Served by rngit 1.5.4 - Generated in 0.1s